package center

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"sync"
	"time"

	"example.com/dcachecenter/logger"
)

type Node struct {
	host     string
	httpPort string
	rpcPort  string
	active   bool
}

func NewNode(host, httpPort, rpcPort string) *Node {
	return &Node{
		host:     host,
		httpPort: httpPort,
		rpcPort:  rpcPort,
		active:   false,
	}
}

func (n *Node) GetHttpAddr() string {
	return n.host + ":" + string(n.httpPort)
}

func (n *Node) GetRpcAddr() string {
	return n.host + ":" + string(n.rpcPort)
}

// Register all nodes to this list, no persist, will lose all data when crash or shutdown
var mu sync.Mutex
var nodesMap map[string]*Node = make(map[string]*Node)

// Return all other nodes except the requesting one
func GetAllNodes(w http.ResponseWriter, r *http.Request) {
	var nodes map[string]string = map[string]string{}

	for _, node := range nodesMap {
		nodes[node.GetHttpAddr()] = node.GetRpcAddr()
	}

	//w.Header().Set("Content/Type", "application/json")
	json.NewEncoder(w).Encode(nodes)
}

// Return all other nodes except the requesting one
func GetAllNodesHttpAddr(w http.ResponseWriter, r *http.Request) {
	var nodes []string

	for _, node := range nodesMap {
		nodes = append(nodes, node.GetHttpAddr())
	}

	//w.Header().Set("Content/Type", "application/json")
	json.NewEncoder(w).Encode(nodes)
}

// Return all other nodes except the requesting one
func GetAllNodesRpcAddr(w http.ResponseWriter, r *http.Request) {
	var nodes []string

	for _, node := range nodesMap {
		nodes = append(nodes, node.GetRpcAddr())
	}

	//w.Header().Set("Content/Type", "application/json")
	json.NewEncoder(w).Encode(nodes)
}

// Choose handler based on path, then send modified nodesList to all nodes
func OperateNodeList(w http.ResponseWriter, r *http.Request) {
	mu.Lock()
	defer mu.Unlock()
	var currentNode string

	switch r.URL.Path {
	case "/RegisterNode":
		currentNode = registerNode(w, r)
	case "":
	}

	GetAllNodes(w, r)
	//GetAllNodesHttpAddr(w, r)
	//GetAllNodesRpcAddr(w, r)

	if err := NodifyOtherNodes(nodesMap[currentNode]); err != nil {
		//w.Write([]byte("sendNodeListToAllNodes: failed"))
		fmt.Println("sendNodeListToAllNodes:", err)
		logger.Logger.Println("sendNodeListToAllNodes:", err)
	}

	nodesMap[currentNode].active = true
}

// Register a node to nodesList
func registerNode(w http.ResponseWriter, r *http.Request) string {
	if err := r.ParseForm(); err != nil {
		logger.Logger.Println("registerNode: Parse form failed")
	}

	//Parse registering node's info
	host := r.Form["host"][0]
	httpPort := r.Form["httpPort"][0]
	rpcPort := r.Form["rpcPort"][0]

	//register node to nodesMap
	//false means it is not available right now, for heartBeat
	node := NewNode(host, httpPort, rpcPort)

	nodesMap[node.GetHttpAddr()] = node
	fmt.Println(nodesMap)
	return node.GetHttpAddr()
}

func isSelf(node1 string, node2 string) bool {
	return node1 == node2
}

// NodifyOtherNodes nodifies other nodes except registering node that
// the nodesList has been modified. It sends node list as json
func NodifyOtherNodes(currentNode *Node) error {
	//Get other nodes
	var nodes []string

	for _, node := range nodesMap {
		if ok := isSelf(currentNode.GetHttpAddr(), node.GetHttpAddr()); !ok {
			nodes = append(nodes, node.GetHttpAddr())
		}
	}

	//Nodify other nodes, send all nodes but self to each node
	var peers map[string]string = make(map[string]string)

	for _, node := range nodes {
		for _, peer := range nodesMap {
			peers[peer.GetHttpAddr()] = peer.GetRpcAddr()
		}

		data, err := json.Marshal(peers)
		if err != nil {
			logger.Logger.Println("peers json marshal failed")
			return fmt.Errorf("peers json marshal failed")
		}
		reader := bytes.NewReader(data)

		//Send the node list to each node, by calling the node's Api, data sent as json
		if _, err := http.Post("http://"+node+"/1.0/util/updateGlobalNodesList", "application/json", reader); err != nil {
			logger.Logger.Println(err)
			return err
		}

		peers = make(map[string]string)
	}

	return nil
}

// HeartBeat pings all nodes around every 2 seconds
// nodesList will be locked by mutex every 2 seconds, other operations cannot modify
// nodesList until mutex is released. It does not impact performance a lot due to modifying
// nodesList is not a frequent operation
func HeartBeat() {
	var deleted bool

	for {
		if len(nodesMap) == 0 {
			fmt.Println(time.Now(), ":  No nodes now")
			logger.Logger.Println(time.Now(), ":  No nodes now")
			time.Sleep(10 * time.Second)
			continue
		}

		mu.Lock()
		for node, nodeStruct := range nodesMap {
			if nodeStruct.active {
				if res, err := http.Get("http://" + nodeStruct.GetHttpAddr() + "/1.0/ping"); err != nil {
					delete(nodesMap, node)
					deleted = true
					fmt.Println(err, "\n", "node has been removed")
					logger.Logger.Println(err, time.Now(), ":  node has been removed")
				} else {
					pong, err := ioutil.ReadAll(res.Body)
					if err != nil || string(pong) != "pong" {
						delete(nodesMap, node)
						deleted = true
						fmt.Println(err, "\n", "node has been removed")
						logger.Logger.Println(err, time.Now(), ":  node has been removed")
					}
				}
			}
		}

		if deleted {
			nodifyAllNodes()
			deleted = false
		}
		mu.Unlock()

		time.Sleep(10 * time.Second)
	}
}

func nodifyAllNodes() error {
	var peers []string
	for _, node := range nodesMap {
		for _, peer := range nodesMap {
			peers = append(peers, peer.GetHttpAddr())
		}

		data, err := json.Marshal(peers)
		if err != nil {
			return fmt.Errorf("peers json marshal failed")
		}
		reader := bytes.NewReader(data)

		//Send the node list to each node, by calling the node's Api, data sent as json
		if _, err := http.Post("http://"+node.GetHttpAddr()+"/1.0/util/updateGlobalNodesList", "application/json", reader); err != nil {
			return err
		}

		peers = peers[0:0]
	}

	return nil
}
